package gr.aueb.cs.mscis.chordroid.app;

import gr.aueb.cs.mscis.chordroid.ChordroidService;
import gr.aueb.cs.mscis.chordroid.chord.NodeIdentifier;
import gr.aueb.cs.mscis.chordroid.util.Sha1;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;

import android.util.Log;

/**
 * This class is handles a file request from the app
 * @author lupin
 *
 */
public class FileLeecher {

	private String filename;
	private FileApp fileApp;
	private FileDescriptor descriptor;
	private ArrayList<FileData> fileBitmap = new ArrayList<FileData>();;
	
	public FileLeecher(FileApp fileApp, String filename) {
		this.fileApp = fileApp;
		this.filename = filename;
	}
	
	public byte[] requestFile() {
		// pare to descriptor
		String filenameHash = Sha1.getHash(filename);
		ChordroidService.getMyLuckyNode().findSuccessor(filenameHash, ChordroidService.getMyLuckyNode().getNodeId());
		
		// perimene gia tn apanthsh
		// panta 8a er8ei kapoia apanthsh
		// akomh kai an to arxeio den uparxei
		// auto 8a to ma8oume otan milhsoume me 
		// thn allh efarmogh
		NodeIdentifier descriptorOwnerAddr = getResponseAddr(filenameHash);
		
		// mila me tn allh efarmogh se addr : port
		descriptor = (FileDescriptor) getFileChunk(filenameHash, descriptorOwnerAddr.getAddr(), descriptorOwnerAddr.getPort());
		if (descriptor == null) return null;
//		if (!descriptor.getHash().equals(filenameHash)) return null; // an to arxeio de vre8hke
		Log.v("Leecher","descr found");

		// an pareis apanthsh pare ta chunks tou arxeiou
		int numOfDataChunks = descriptor.getNumberOfFileChunks();
		for (int i=0; i<numOfDataChunks; i++) {
			int replica = 1;
			// filename-datachunkNo-replicaNo
			String chunkHash = Sha1.getHash(filename + "-" + i + "-" + replica);
			ChordroidService.getMyLuckyNode().findSuccessor(chunkHash, ChordroidService.getMyLuckyNode().getNodeId());
			Log.v("Leecher", "find hash: " + chunkHash);
		}
		
		// split the iteration		
		for (int i=0; i<numOfDataChunks; i++) {
			int replica = 1;
			String chunkHash = Sha1.getHash(filename + "-" + i + "-" + replica);
			NodeIdentifier chunkOwnerAddr = getResponseAddr(chunkHash);

			FileData dataChunk = (FileData) getFileChunk(chunkHash, chunkOwnerAddr.getAddr(), chunkOwnerAddr.getPort());
			if (dataChunk == null) Log.v("Leecher", "null data");

			Log.v("Leecher", "data" + dataChunk.getHash());
			
			
			// if dataChunk not found try the replicas
			while (dataChunk == null && replica<descriptor.getNumOfReplicas()) {
				// calculate new hash
				replica++;
				chunkHash = Sha1.getHash(filename + "-" + i + "-" + replica);
				// try to get the replica
				ChordroidService.getMyLuckyNode().findSuccessor(chunkHash, ChordroidService.getMyLuckyNode().getNodeId());
				chunkOwnerAddr = getResponseAddr(chunkHash);
				dataChunk = (FileData) getFileChunk(chunkHash, chunkOwnerAddr.getAddr(), chunkOwnerAddr.getPort());
			}
			if (dataChunk == null) dataChunk = new FileData();

			// add data chunk to bitmap
			fileBitmap.add(i, dataChunk);
		}
		
		return assembleFile();
	}
	
	private byte[] assembleFile() {
					
		// dummy method, learn where to save the file
		byte[] out = new byte[descriptor.getFileSize()];
		for (int i=0; i<descriptor.getNumberOfFileChunks(); i++) {
			System.arraycopy(fileBitmap.get(i).getData(), 0, out, i*descriptor.getFileChunkSize(), fileBitmap.get(i).getData().length);
		}
		if (descriptor.getSynopsis().equals(Sha1.getHash(out))) Log.v("Leecher", "correct synopsis");// then the synopsis is correct;
		
		return out;
	}

	private NodeIdentifier getResponseAddr(String hash) {
		NodeIdentifier successorAddr = fileApp.getResponseByHash(hash);
		int i=0;
		while (successorAddr == null && i < 10) {
			try {
				// relax
				Log.v("leeder","waiting for " + i++ + " time");
				// try again
				successorAddr = fileApp.getResponseByHash(hash);
				ChordroidService.getMyLuckyNode().findSuccessor(hash, ChordroidService.getMyLuckyNode().getNodeId());
				Thread.sleep(i*1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		fileApp.getHashMap().remove(hash);
		return successorAddr;
	}

	private FileChunk getFileChunk(String hash, InetAddress address, int port) {
		try {
		Socket outToAnotherFileApp = new Socket(address, port + 2); /* i efarmofh akouei se +2 apo to deamon tou chord */
		
		ObjectOutputStream oos = new ObjectOutputStream(outToAnotherFileApp.getOutputStream());
		oos.writeObject(hash);
		oos.flush();
		
		ObjectInputStream ois = new ObjectInputStream(outToAnotherFileApp.getInputStream());
		return (FileChunk) ois.readObject();

		} catch (EOFException eofe) {
			return null;
		} catch (SocketException se) {
			return null;
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

}
